package com.hotacorp.opencloud.gateway.filter.factory;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSONObject;
import com.hotacorp.opencloud.common.commdata.Constants;
import com.hotacorp.opencloud.common.commdata.Result;
import com.hotacorp.opencloud.gateway.feign.TokenInfoFeignClient;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 自定义复合条件流控过滤器
 * @author lwg
 *
 */
public class CompositeRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<CompositeRateLimiterGatewayFilterFactory.Config> {

	private static final String EMPTY_KEY = "____EMPTY_KEY__";

	private final RateLimiter<?> defaultRateLimiter;
	
	@Autowired
	@Lazy
	private TokenInfoFeignClient tokenInfoFeignClient;

	/** Switch to deny requests if the Key Resolver returns an empty key, defaults to true. */
	private boolean denyEmptyKey = true;

	/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
	private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();

	public CompositeRateLimiterGatewayFilterFactory(RateLimiter<?> defaultRateLimiter) {
		super(Config.class);
		this.defaultRateLimiter = defaultRateLimiter;
	}

	public RateLimiter<?> getDefaultRateLimiter() {
		return defaultRateLimiter;
	}

	public boolean isDenyEmptyKey() {
		return denyEmptyKey;
	}

	public void setDenyEmptyKey(boolean denyEmptyKey) {
		this.denyEmptyKey = denyEmptyKey;
	}

	public String getEmptyKeyStatusCode() {
		return emptyKeyStatusCode;
	}

	public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
		this.emptyKeyStatusCode = emptyKeyStatusCode;
	}

	@Override
	public GatewayFilter apply(Config config) {
		RateLimiter<?> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
		boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);

		return (exchange, chain) -> {
			HttpMethod httpMethod = exchange.getRequest().getMethod();
			if (!config.getMethods().contains(httpMethod)) {
				return chain.filter(exchange);
			}
			
			Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
			Mono<String> monostr = Mono.empty();;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append(config.getGroupId());
			stringBuilder.append("-");
			if (config.originKey) {
				stringBuilder.append(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
			}
			boolean logined = true;
			if (config.clientidKey || config.useridKey) {
				JSONObject userinf = (JSONObject)exchange.getAttributes().get(Constants.USER_INFO);
				if (userinf == null) {
					List<String> aaa1 = exchange.getRequest().getQueryParams().getOrDefault("access_token", Collections.emptyList());
					List<String> aaa2 = exchange.getRequest().getHeaders().getOrDefault(HttpHeaders.AUTHORIZATION, Collections.emptyList());
					if (!aaa1.isEmpty() || !aaa2.isEmpty()) {
						String access_token = "";
						if (!aaa2.isEmpty()) {
							access_token = aaa2.get(0).replace("Bearer", "").trim();
						} else {
							access_token = aaa1.get(0);
						}
						String infostr = tokenInfoFeignClient.getTokenInfo(access_token);
						if (infostr != null && StringUtils.contains(infostr, "\"client_id\":\"") && StringUtils.contains(infostr, "\"user_id\":") && StringUtils.startsWith(infostr, "{") && StringUtils.endsWith(infostr, "}")) {
							JSONObject sfas = JSONObject.parseObject(infostr);
							exchange.getAttributes().put(Constants.USER_INFO, sfas);
						}
					}
				}
			}
			stringBuilder.append("-");
	        if (config.clientidKey) {
	        	JSONObject userinf = (JSONObject)exchange.getAttributes().get(Constants.USER_INFO);
	        	if (userinf == null) {
	        		logined = false;
	        	} else {
					stringBuilder.append(userinf.getString("client_id"));
	        	}
	        }
			stringBuilder.append("-");
	        if (config.useridKey) {
	        	JSONObject userinf = (JSONObject)exchange.getAttributes().get(Constants.USER_INFO);
	        	if (userinf == null) {
	        		logined = false;
	        	} else {
					stringBuilder.append(userinf.getString(Constants.USER_ID));
	        	}
	        }
			if (logined) {
				monostr = Mono.just(stringBuilder.toString());
			}
			return monostr.defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
				if (EMPTY_KEY.equals(key)) {
					if (denyEmpty) {
						setResponseStatus(exchange, HttpStatus.OK);
			            Flux<DataBuffer> bodyFlux = genDB(exchange, HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
			            
			            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
			            return exchange.getResponse().writeWith(bodyFlux).then(Mono.fromRunnable(() ->{exchange.getResponse().setComplete();}));
					}
					return chain.filter(exchange);
				}
				return limiter.isAllowed(route.getId(), key).flatMap(response -> {

					for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
						exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
					}

					if (response.isAllowed()) {
						return chain.filter(exchange);
					}

					setResponseStatus(exchange, HttpStatus.OK);
		            Flux<DataBuffer> bodyFlux = genDB(exchange, HttpStatus.TOO_MANY_REQUESTS.value(), "超速");
		            
		            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
		            return exchange.getResponse().writeWith(bodyFlux).then(Mono.fromRunnable(() ->{exchange.getResponse().setComplete();}));
				});
			});
		};
	}

	private <T> T getOrDefault(T configValue, T defaultValue) {
		return (configValue != null) ? configValue : defaultValue;
	}
	
	private Flux<DataBuffer> genDB(ServerWebExchange exchange, int code, String msg) {
		Result<String> ddddd = new Result<String>("", code, msg);
		String string = JSONObject.toJSONString(ddddd);
		byte[] bodyStr = new byte[0];
		try {
			bodyStr = string.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		DataBufferFactory sss= exchange.getResponse().bufferFactory();
		DataBuffer bodyDataBuffer = sss.wrap(bodyStr);
        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
        return bodyFlux;
	}

	public static class Config {
		private RateLimiter<?> rateLimiter;
		private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
		private Boolean denyEmptyKey;
		private String emptyKeyStatus;
		private Boolean clientidKey = false;
		private Boolean useridKey = false;
		private Boolean originKey = false;
		private String groupId = "groupId";
		private List<HttpMethod> methods = new ArrayList<>(Arrays.asList(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.POST));

		public String getGroupId() {
			return groupId;
		}

		public void setGroupId(String groupId) {
			this.groupId = groupId;
		}

		public Boolean getClientidKey() {
			return clientidKey;
		}

		public Config setClientidKey(Boolean clientidKey) {
			this.clientidKey = clientidKey;
			return this;
		}

		public Boolean getUseridKey() {
			return useridKey;
		}

		public Config setUseridKey(Boolean useridKey) {
			this.useridKey = useridKey;
			return this;
		}

		public Boolean getOriginKey() {
			return originKey;
		}

		public Config setOriginKey(Boolean originKey) {
			this.originKey = originKey;
			return this;
		}

		public List<HttpMethod> getMethods() {
			return methods;
		}

		public Config setMethods(List<HttpMethod> methods) {
			this.methods = methods;
			return this;
		}

		public RateLimiter<?> getRateLimiter() {
			return rateLimiter;
		}

		public Config setRateLimiter(RateLimiter<?> rateLimiter) {
			this.rateLimiter = rateLimiter;
			return this;
		}

		public HttpStatus getStatusCode() {
			return statusCode;
		}

		public Config setStatusCode(HttpStatus statusCode) {
			this.statusCode = statusCode;
			return this;
		}

		public Boolean getDenyEmptyKey() {
			return denyEmptyKey;
		}

		public Config setDenyEmptyKey(Boolean denyEmptyKey) {
			this.denyEmptyKey = denyEmptyKey;
			return this;
		}

		public String getEmptyKeyStatus() {
			return emptyKeyStatus;
		}

		public Config setEmptyKeyStatus(String emptyKeyStatus) {
			this.emptyKeyStatus = emptyKeyStatus;
			return this;
		}
	}

}
